/// <reference path="../helpers.ts" />
/// <reference path="../types.ts" />

// todo: need a better way to handle this xml building and namespace handing, too ugly and hard to maintain...
module $Xrm.Messages {
    export class OrganizationRequest {
        public static XmlNamespaces = {
            "a": "http://schemas.microsoft.com/xrm/2011/Contracts",
            "b": "http://schemas.microsoft.com/crm/2011/Contracts",
            "c": "http://schemas.datacontract.org/2004/07/System.Collections.Generic",
            "i": "http://www.w3.org/2001/XMLSchema-instance",
            "metadata": "http://schemas.microsoft.com/xrm/2011/Metadata"
        };

        public RequestId: string;
        public RequestName: string;
        public Parameters: any[];

        private _requestNamespace: string = "a"; // default to A unless stated otherwise

        constructor(requestName?: string, requestNamespace?: string) {
            this.RequestName = requestName;
            this.Parameters = [];

            if (requestNamespace != null && requestName != undefined) { this._requestNamespace = requestNamespace; }
        }

        GetResponse(responseXml: Document): OrganizationResponse {
            return new OrganizationResponse();
        }

        ToSoapXml(): string {
            var xml = _string.format("<request i:type=\"{0}:{1}Request\" xmlns:a=\"{2}\" xmlns:b=\"{3}\">",
                this._requestNamespace,
                this.RequestName,
                OrganizationRequest.XmlNamespaces["a"],
                OrganizationRequest.XmlNamespaces["b"]
            );

            xml += this.BuildParameterSoapXml();

            if (this.RequestId != null && this.RequestName != undefined) {
                xml += _string.format("<a:RequestId>{0}</a:RequestId>", this.RequestId);
            }
            else { xml += "<a:RequestId i:nil=\"true\" />"; }

            xml += _string.format("<a:RequestName>{0}</a:RequestName></request>", this.RequestName);

            return xml;
        }

        private BuildParameterSoapXml() {
            var xml = _string.format("<a:Parameters xmlns:c=\"{0}\"", OrganizationRequest.XmlNamespaces["c"]);

            if (_array.getLength(this.Parameters) > 0) {
                xml += ">"; // this is not a mistake, if we have parameters we'll append closing element and add the keys, otherwise we'll append /> and self close

                for (var key in this.Parameters) {
                    var value = this.Parameters[key];

                    xml += "<a:KeyValuePairOfstringanyType>";
                    xml += _string.format("<c:key>{0}</c:key>", key);
                    xml += this.BuildParameterValue(key, value);

                    xml += "</a:KeyValuePairOfstringanyType>";
                }
                xml += "</a:Parameters>";
            } else {
                xml += " />";
            }

            return xml;
        }

        private BuildEntityParameters(value: $Xrm.Entity, valueXml: string): string {
            if (_array.getLength(value) > 0) {
                valueXml += "<a:Attributes>";

                for (var attribute in value) {
                    // skip known internal properties. these are useful to the user but has to be ignored when being serialized
                    if (attribute == "LogicalName" || attribute == "Id" || attribute == "get" || attribute == "GetAttributeValue") {
                        continue;
                    }

                    var innerValue = value[attribute];

                    valueXml += _string.format("<a:KeyValuePairOfstringanyType><c:key>{0}</c:key>", attribute);
                    valueXml += this.BuildParameterValue(attribute, innerValue);

                    valueXml += "</a:KeyValuePairOfstringanyType>";
                }

                valueXml += "</a:Attributes>";
            }
            else {
                valueXml += "<a:Attributes i:nil=\"true\" />";
            }

            // todo: check if entitystate element is required on all requests
            valueXml += _string.format("<a:EntityState i:nil=\"true\" /><a:FormattedValues /><a:Id>{0}</a:Id><a:LogicalName>{1}</a:LogicalName><a:RelatedEntities />",
                value.Id != null && value.Id != undefined ? value.Id : "00000000-0000-0000-0000-000000000000",
                value.LogicalName);

            return valueXml;
        }

        private BuildParameterValue(key: string, value: any): string {
            var type = "";
            var valueXml = "";
            var additionalNS = "";

            if (value instanceof $Xrm.Entity) {
                type = "a:Entity";
                valueXml = this.BuildEntityParameters(value, valueXml);
            }
            else if (value instanceof $Xrm.EntityReference) {
                type = "a:EntityReference";
                valueXml = this.BuildEntityReferenceValueParameter(value);
            }
            else if (value instanceof $Xrm.FetchExpression) {
                type = "a:FetchExpression";
                valueXml = _string.format("<a:Query>{0}</a:Query>", value.Query);
            }
            else if (value instanceof $Xrm.OptionSetValue) {
                type = "a:OptionSetValue";
                valueXml = _string.format("<a:Value>{0}</a:Value>", value.Value);
            }
            else if (key == "ColumnSet") { // we need to fuck around with allcolumns=true/false and columnset...
                type = "a:ColumnSet";
                valueXml = _string.format("<a:AllColumns>{0}</a:AllColumns>", value.constructor == Boolean ? value : false);
                valueXml += "<a:Columns xmlns:z=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\"";

                if (value.constructor == Boolean && value) { // if it's all columns; selfclose and be done
                    valueXml += " />";
                } else {
                    valueXml += _string.format(">{0}</a:Columns>", _array.joinWithFormat(value, "<z:string>{0}</z:string>"));
                }
            }
            else if (key == "EntityFilters") {
                type = "metadata:EntityFilters";
                additionalNS = _string.format(" xmlns:metadata=\"{0}\"", OrganizationRequest.XmlNamespaces["metadata"]);
                valueXml = value;
            }
            else if (value instanceof $Xrm.Money) {
                type = "a:Money";
                valueXml = _string.format("<a:Value>{0}</a:Value>", value.Value);
            }
            else if (value instanceof $Xrm.OptionSetValue) {
                type = "a:OptionSetValue";
                valueXml = _string.format("<a:Value>{0}</a:Value>", value.Value);
            }
            else if (value instanceof $Xrm.EntityReference) {
                type = "a:EntityReference";
                valueXml = this.BuildEntityReferenceValueParameter(value);
            }
            else if (_string.isGuid(value)) {
                type = "z:guid";
                additionalNS = " xmlns:z=\"http://schemas.microsoft.com/2003/10/Serialization/\"";
                valueXml = value;
            }
            else {
                // todo: cleanup; too difficult to maintain this condensed if/else
                type = value instanceof Date ? "dateTime"
                    : value.constructor == String ? "string"
                        : value.constructor == Boolean ? "boolean"
                            : (typeof value === 'number' && value % 1 == 0) ? "int"
                                : (/^(\d+\.?\d{0,9}|\.\d{1,9})$/.test(value)) ? "decimal" : "";

                if (type != "") {
                    type = "z:" + type;
                    additionalNS = " xmlns:z=\"http://www.w3.org/2001/XMLSchema\"";
                    valueXml = value instanceof Date ? _date.format(value, "yyyy-mm-ddTHH:MM:ss.Lo") : value;
                }
            }

            return _string.format("<c:value i:type=\"{0}\"{1}>{2}</c:value>", type, additionalNS, valueXml);
        }

        private BuildEntityReferenceValueParameter(value: $Xrm.EntityReference): string {
            var valueXml = _string.format("<a:Id>{0}</a:Id><a:LogicalName>{1}</a:LogicalName>", value.Id, value.LogicalName);
            if (value.Name != null && value.Name != undefined) {
                valueXml += _string.format("<a:Name></a:Name>", value.Name);
            } else {
                valueXml += "<a:Name i:nil=\"true\" />";
            }

            return valueXml;
        }
    }

    export class OrganizationResponse {
        public ResponseName: string;
        public Results: any[];

        constructor(responseName?: string, responseXml?: Document) {
            this.ResponseName = responseName;
            this.Results = [];

            if (responseXml != null && responseXml != undefined) {
                this.ParseResponse(responseXml);
            }
        }

        public BindProperties(): void {
        }

        public ParseResponse(responseXml: Document): void {
            var rn = _xml.getChildNode(responseXml.firstChild.firstChild.firstChild.firstChild, 'a:Results');
            if (rn != null) {
                for (var i = 0; i < rn.childNodes.length; i++) {
                    var current = rn.childNodes[i];

                    var key = _xml.getText(current.childNodes[0]);
                    var value = _xml.getText(current.childNodes[1]);

                    this.Results[key] = value;
                }
            }
        }
    }
}